/*
-- IOTA Crypto Core
--
-- 2018 by Thomas Pototschnig <microengineer18@gmail.com>
-- discord: pmaxuw#8292
-- https://gitlab.com/iccfpga-rv
--
-- Permission is hereby granted, free of charge, to any person obtaining
-- a copy of this software and associated documentation files (the
-- "Software"), to deal in the Software without restriction, including
-- without limitation the rights to use, copy, modify, merge, publish,
-- distribute, sublicense, and/or sell copies of the Software, and to
-- permit persons to whom the Software is furnished to do so, subject to
-- the following conditions:
--
-- The above copyright notice and this permission notice shall be
-- included in all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWAR
*/

#include "main.h"
#include "utils.h"
#include "ssd1331/ssd1331.h"
#include "gpio/gpio.h"
#include "Timer.h"

using namespace SSD1331;

#define min(a,b) ((a<b)?(a):(b))
#define max(a,b) ((a>b)?(a):(b))

extern Timer timer;

namespace Buttons {
	enum Button {
		BTN_TL = (1<<6),
		BTN_TR = (1<<5),
		BTN_BL = (1<<14),
		BTN_BR = (1<<13),
		BTN_ALL = (BTN_TL | BTN_TR | BTN_BL | BTN_BR),
	};

	bool init() {
		GPIO::setDir(BTN_ALL, 0);
		return true;
	}

	void debounce() {
		timer.sleep(500);
	}

	Button waitForButton() {
		while (1) {
			uint32_t buttons = GPIO::read() & BTN_ALL;
			if (buttons) {
				debounce();
				switch (buttons) {
				case BTN_TL:
				case BTN_TR:
				case BTN_BL:
				case BTN_BR:
					return static_cast<Button>(buttons);
				default:
					;
				}
			}
		}
	}
}

namespace UI {
#if 0
	while (1) {
		uint32_t val = GPIO::read() & BUTTONS;
		switch (val) {
		case BTN_TL: trace_printf("top left\n"); break;
		case BTN_TR: trace_printf("top right\n"); break;
		case BTN_BL: trace_printf("bottom left\n"); break;
		case BTN_BR: trace_printf("bottom right\n"); break;
		}

		timer.sleep(1000);
	}
	while (1) ;
	//Tests::all();
#endif


	namespace {


		const char* MMTEXT getTypeString(Transaction::Type type) {
			switch (type) {
			case Transaction::Input:
				return "INPUT";
			case Transaction::Output:
				return "OUTPUT";
			case Transaction::Remainder:
				return "CHANGE";
			default:
				return 0;
			}
		}

		bool MMTEXT formatValue(int64_t value, char* buf, size_t bufSize) {
			if (value < 0ll || value > 9999999999999ll)	// limit 9.99...9Ti
				return false;

			// max-length: 9.999999999999Ti (16 + 1 zero)
			if (bufSize < 17)
				return false;

			int conv[5]={0};
			int idx = -1;
			do {
				conv[++idx] = value % 1000ll;
				value /= 1000ll;
			} while (value > 0ll);

			const char* pattern[]={"%d.", "%03d"};
			int written = 0;
			int msd = idx;
			while (idx >= 0) {
				written += snprintf(&buf[written], bufSize-written, (msd == (idx)) ? pattern[0] : pattern[1], conv[idx]);
				idx--;
			}

			const char* units[]={"i", "ki", "Mi", "Gi", "Ti"};
			if (msd < 0 || msd > 4) {
				return false;
			}
			snprintf(&buf[written], bufSize-written, "%s", units[msd]);
			return true;
		}

		bool MMTEXT abbrevAddress(const char* address, char* buf, size_t bufSize) {
			if (!validateTrytes(address, 81, false)) {
				return false;
			}
			// "AOTJAEQ...XDBAAA"
			if (bufSize != 17) {
				return false;
			}
			memcpy(&buf[0], &address[0], 6);
			memcpy(&buf[6], "...", 3);
			memcpy(&buf[9], &address[81-7], 7);
			buf[16] = 0;
			return true;
		}
	}

	bool MMTEXT displayConfirm() {
		drawFrame(0, 0, 96, 64, COLOR_BLACK, COLOR_BLACK);
		timer.sleep(1);

		Font5x7 font;
		Font3x7 font3x7;

		setXY(0, 8+8);
		printStringCentered(font, "Accept?", COLOR_VFD, COLOR_BLACK);

		setXY(0, 64-23);
		printString(font, "     ",  COLOR_BLACK, COLOR_VFD);
		setXY(0, 64-16);
		printString(font, " YES ",  COLOR_BLACK, COLOR_VFD);
		setXY(0, 64-9);
		printString(font, "     ",  COLOR_BLACK, COLOR_VFD);


		setXY(96-5*6, 64-23);
		printString(font, "     ",  COLOR_BLACK, COLOR_VFD);
		setXY(96-5*6, 64-16);
		printString(font, "  NO ",  COLOR_BLACK, COLOR_VFD);
		setXY(96-5*6, 64-9);
		printString(font, "     ",  COLOR_BLACK, COLOR_VFD);
		return true;
	}

	bool MMTEXT displayTransaction(Transaction* tx, bool first, bool last) {
		int64_t value = tx->getValue();
		value = (value < 0ll) ? -value : value;

		char valueBuf[32];
		if (!formatValue(value, valueBuf, sizeof(valueBuf)))
			return false;

		const char* type = getTypeString(tx->getType());
		if (!type)
			return false;

		char addr[17];
		if (!abbrevAddress(tx->getAddress(), addr, sizeof(addr)))
			return false;

		char bundle[17];
		if (!abbrevAddress(tx->getBundle(), bundle, sizeof(bundle)))
			return false;

		char fullAddress[91] = {0}; // with zero terminator
		tx->getFullAddress(fullAddress);	// uses hashing to calculate new checksum
		char* checksum = &fullAddress[81];

		drawFrame(0, 0, 96, 64, COLOR_BLACK, COLOR_BLACK);
		timer.sleep(1);

		Font5x7 font;
		Font3x7 font3x7;

		printStringCentered(font, type, COLOR_VFD, COLOR_BLACK);
		setXY(0, 0*8+4);

		setXY(0, 0*8+4);
		putChar(font, (!first) ? 127 : ' ', COLOR_VFD, COLOR_BLACK);

		setXY(96-6, 0*8+4);
		putChar(font, (!last) ? 126 : ' ', COLOR_VFD, COLOR_BLACK);

		setXY(0, 2*8+4);
		printStringCentered(font, valueBuf, COLOR_WHITE, COLOR_BLACK);

		setXY(0, 3*8+4+4);
		printStringCentered(font, addr, COLOR_LIGHTVFD, COLOR_BLACK);

		setXY(0, 4*8+4+4);
		printStringCentered(font3x7, checksum, COLOR_DARKVFD, COLOR_BLACK);

		setXY(0, 6*8);
		//SSD1331::printString(font3x7, "BUNDLE", SSD1331::COLOR_DARKVFD, SSD1331::COLOR_BLACK);

		setXY(0, 7*8);
		printStringCentered(font, bundle, COLOR_VFD, COLOR_BLACK);
		return true;
	}

	bool MMTEXT confirmTransactions(uint32_t numTX) {
		if (!numTX)
			return false;

		Buttons::init();

		uint32_t page = 0;
		// signatures are not shown because:
		// Transfers::prepareTransfers uses chaining to "insert" signatures but the array itself up to numTX
		// only contains transactions that were given to the SBI call and inserted transactions come afterwards
		// so this is fine
		for (;;) {
			if (page == numTX) {	// show confirm page
				if (!displayConfirm())
					return false;
			} else {				// show transaction page
				if (!displayTransaction(&txs[page], page == 0, page == numTX-1)) {
					return false;
				}
			}
			Buttons::Button btn = Buttons::waitForButton();

			switch (btn) {
			case Buttons::BTN_TL:
				page = (page == 0) ? 0 : page-1;
				break;
			case Buttons::BTN_TR:
				page = (page == numTX) ? numTX : page + 1;
				break;
			case Buttons::BTN_BR:
				if (page == numTX) {
					return false;
				}
				break;
			case Buttons::BTN_BL:
				if (page == numTX) {
					return true;
				}
				break;
			default:
				;
			}
		}
		return true;
	}

}
